前言
spring boot 中关于Log的实现我们已经分析了JavaLoggingSystem,本文就来看看Log4J2LoggingSystem,在分析之前,我们需要先分析一下Slf4JLoggingSystem–> Log4J2LoggingSystem,LogbackLoggingSystem 的父类.
解析
Slf4JLoggingSystem
字段,构造器如下:
private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler"; public Slf4JLoggingSystem(ClassLoader classLoader) { super(classLoader); }
覆写了如下方法:
beforeInitialize,代码如下:
public void beforeInitialize() { super.beforeInitialize(); // 1. 配置SLF4JBridgeHandler configureJdkLoggingBridgeHandler(); }
configureJdkLoggingBridgeHandler–>配置SLF4JBridgeHandler 代码如下:
private void configureJdkLoggingBridgeHandler() { try { // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则 if (isBridgeHandlerAvailable()) { // 1.1 删除slf4j 中root logger 配置的所有handler removeJdkLoggingBridgeHandler(); // 1.2 为root logger添加SLF4JBridgeHandler SLF4JBridgeHandler.install(); } } catch (Throwable ex) { // Ignore. No java.util.logging bridge is installed. } }
如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则
删除slf4j 中root logger 配置的所有handler,代码如下:
private void removeJdkLoggingBridgeHandler() { try { if (isBridgeHandlerAvailable()) { try { SLF4JBridgeHandler.removeHandlersForRootLogger(); } catch (NoSuchMethodError ex) { // Method missing in older versions of SLF4J like in JBoss AS 7.1 SLF4JBridgeHandler.uninstall(); } } } catch (Throwable ex) { // Ignore and continue } }
为root logger添加SLF4JBridgeHandler,代码如下:
public static void install() { LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler()); }
cleanUp–>删除slf4j 中root logger 配置的所有handler,代码如下:
public void cleanUp() { removeJdkLoggingBridgeHandler(); }
loadConfiguration–>设置系统属性,代码如下:
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); if (initializationContext != null) { applySystemProperties(initializationContext.getEnvironment(), logFile); } }
调用父类(AbstractLoggingSystem)中的 applySystemProperties方法,代码如下:
protected final void applySystemProperties(Environment environment, LogFile logFile) { new LoggingSystemProperties(environment).apply(logFile); }
Log4J2LoggingSystem
字段如下:
private static final String FILE_PROTOCOL = "file"; private static final LogLevels<Level> LEVELS = new LogLevels<Level>(); static { LEVELS.map(LogLevel.TRACE, Level.TRACE); LEVELS.map(LogLevel.DEBUG, Level.DEBUG); LEVELS.map(LogLevel.INFO, Level.INFO); LEVELS.map(LogLevel.WARN, Level.WARN); LEVELS.map(LogLevel.ERROR, Level.ERROR); LEVELS.map(LogLevel.FATAL, Level.FATAL); LEVELS.map(LogLevel.OFF, Level.OFF); } // 在beforeInitialize中添加该filter,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了 private static final Filter FILTER = new AbstractFilter() { @Override public Result filter(LogEvent event) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { return Result.DENY; } }; public Log4J2LoggingSystem(ClassLoader classLoader) { super(classLoader); }
方法如下:
getStandardConfigLocations –> 获取配置文件,代码如下:
protected String[] getStandardConfigLocations() { return getCurrentlySupportedConfigLocations(); }
调用:
private String[] getCurrentlySupportedConfigLocations() { List<String> supportedConfigLocations = new ArrayList<String>(); // 1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持 if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) { Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml"); } // 2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) { Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn"); } // 3. 默认加入log4j2.xml supportedConfigLocations.add("log4j2.xml"); return supportedConfigLocations .toArray(new String[supportedConfigLocations.size()]); }
- 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持
- 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn
- 默认加入log4j2.xml
因此,默认情况下返回的是log4j2.json, log4j2.jsn, log4j2.xml
beforeInitialize,代码如下:
public void beforeInitialize() { LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } super.beforeInitialize(); loggerContext.getConfiguration().addFilter(FILTER); }
- 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
- 调用父类的beforeInitialize,配置SLF4JBridgeHandler
- 添加FILTER,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
initialize,代码如下:
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // 1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem, // 则意味着已经初始化过了,此时直接return LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } // 2. 删除FILTER,此时意味着已经初始化成功了 loggerContext.getConfiguration().removeFilter(FILTER); // 3. 加载配置文件 super.initialize(initializationContext, configLocation, logFile); // 4. 向LoggerContext中的ExternalContext 存放-->org.springframework.boot.logging.LoggingSystem,标记成功初始化 markAsInitialized(loggerContext); }
- 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
- 删除FILTER,此时意味着已经初始化成功了
- 加载配置文件
向LoggerContext中的ExternalContext 存放–>org.springframework.boot.logging.LoggingSystem,标记成功初始化.代码如下:
private void markAsInitialized(LoggerContext loggerContext) { loggerContext.setExternalContext(LoggingSystem.class.getName()); }
loadDefaults–>加载默认的配置文件:
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { // 1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的 if (logFile != null) { loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile); } // 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml else { loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile); } }
- 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的
- 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml
loadConfiguration实现如下:
protected void loadConfiguration(String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); try { // 1. 获得LoggerContext LoggerContext ctx = getLoggerContext(); // 2.将location转换为URL URL url = ResourceUtils.getURL(location); // 3.根据url 获得对应的ConfigurationSource ConfigurationSource source = getConfigurationSource(url); // 4. 启动 ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source)); } catch (Exception ex) { throw new IllegalStateException( "Could not initialize Log4J2 logging from " + location, ex); } }
- 获得LoggerContext
- 将location转换为URL
根据url 获得对应的ConfigurationSource.代码如下:
private ConfigurationSource getConfigurationSource(URL url) throws IOException { InputStream stream = url.openStream(); if (FILE_PROTOCOL.equals(url.getProtocol())) {// 如果是file协议 return new ConfigurationSource(stream, ResourceUtils.getFile(url)); } // 2. 其他,由于当前是classpath 协议,因此会执行到这里 return new ConfigurationSource(stream, url); }
- 启动
reinitialize –>调用时机:在log4j初始化时,在类路径下加载的以下任一1个配置文件:log4j2.json, log4j2.jsn, log4j2.xml,通过LogFile等于null,则调用该方法.代码如下:
protected void reinitialize(LoggingInitializationContext initializationContext) { getLoggerContext().reconfigure(); }
log4j 不会直接删除所有的Loggers在重新配置的阶段,而是会重新创建LoggerConfig,然后进行替换.旧的LoggerConfig,Appenders,Filters 会被释放
setLogLevel.代码如下:
public void setLogLevel(String loggerName, LogLevel logLevel) { // 1. 转换LogLevel 为log4j2的LogLevel Level level = LEVELS.convertSystemToNative(logLevel); // 2.根据loggerName 获得对应的LoggerConfig LoggerConfig loggerConfig = getLoggerConfig(loggerName); // 3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改 if (loggerConfig == null) { loggerConfig = new LoggerConfig(loggerName, level, true); getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig); } else { loggerConfig.setLevel(level); } // 4. 更新 getLoggerContext().updateLoggers(); }
- 转换LogLevel 为log4j2的LogLevel
- 根据loggerName 获得对应的LoggerConfig
- 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改
- 更新
getLoggerConfigurations –> 获得所有的配置文件.代码如下:
public List<LoggerConfiguration> getLoggerConfigurations() { List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>(); Configuration configuration = getLoggerContext().getConfiguration(); for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { result.add(convertLoggerConfiguration(loggerConfig)); } Collections.sort(result, CONFIGURATION_COMPARATOR); return result; }
- 获得Configuration中配置的Logger,遍历之
将LoggerConfig 转换为LoggerConfiguration.代码如下:
private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) { if (loggerConfig == null) { return null; } LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); String name = loggerConfig.getName(); if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) { name = ROOT_LOGGER_NAME; } return new LoggerConfiguration(name, level, level); }
- 如果LoggerConfig等于null,则返回null
- 将log4j配置的日志级别转换为LogLevel
- 如果logger的名字等于root,或者不存在,则将其赋值为root
- 实例化LoggerConfiguration
- 排序–>将root logger 排在第1位,其他的按照字典顺序排序
getShutdownHandler–>在ShutdownHandler中直接调用LoggerContext的stop方法.代码如下:
public Runnable getShutdownHandler() { return new ShutdownHandler(); }
ShutdownHandler 代码如下:
private final class ShutdownHandler implements Runnable { @Override public void run() { getLoggerContext().stop(); } }
cleanUp–>调用时机,当spring boot 发出ContextClosedEvent事件时调用,代码如下:
public void cleanUp() { super.cleanUp(); LoggerContext loggerContext = getLoggerContext(); markAsUninitialized(loggerContext); loggerContext.getConfiguration().removeFilter(FILTER); }
- 调用父类的cleanUp方法,删除rootLogger配置的handler
- 获得LoggerContext
设置ExternalContext 为null,代码如下:
private void markAsUninitialized(LoggerContext loggerContext) { loggerContext.setExternalContext(null); }
- 删除FILTER
Log4J2LoggingSystem 集成
由于spring boot 默认依赖的logback,因此我们需要去除,修改pom文件如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
加入 Log4J2LoggingSystem的依赖.pom文件如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
Log4J2LoggingSystem生命周期
由于SpringBootConfigurationFactory继承了ConfigurationFactory,因此当使用Log4J2LoggingSystem 的时候,由于log还没有初始化,此时先使用DefaultConfiguration,后续会使用指定的配置.代码如下:
@Plugin(name = "SpringBootConfigurationFactory", category = ConfigurationFactory.CATEGORY) @Order(0) public class SpringBootConfigurationFactory extends ConfigurationFactory { private static final String[] TYPES = { ".springboot" }; @Override protected String[] getSupportedTypes() { return TYPES; } @Override public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) { if (source != null && source != ConfigurationSource.NULL_SOURCE) { if (LoggingSystem.get(loggerContext.getClass().getClassLoader()) != null) { return new DefaultConfiguration(); } } return null; } }
当在类路径下存在.springboot结尾的文件,则会调用该类的getConfiguration方法.由于在spring-boot/src/main/resources/ 下,存在log4j2.springboot,因此该类会被执行.其文件内容如下:
See SpringBootConfigurationFactory
此时由于传入的ConfigurationSource不等于null,此时传入的是log4j2.springboot,因此会返回DefaultConfiguration
ApplicationStartingEvent事件处理–>执行Log4J2LoggingSystem#beforeInitialize方法.在该方法中会调用Slf4JLoggingSystem#configureJdkLoggingBridgeHandler,会判断org.slf4j.bridge.SLF4JBridgeHandler是否存在,此时由于加入了spring-boot-starter-log4j2,因此加入了jul-to-slf4 jar 包, 该类就是在该包中,因此会执行后续操作,代码如下:
private void configureJdkLoggingBridgeHandler() { try { // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则 if (isBridgeHandlerAvailable()) { // 1.1 删除slf4j 中root logger 配置的所有handler removeJdkLoggingBridgeHandler(); // 1.2 为root logger添加SLF4JBridgeHandler SLF4JBridgeHandler.install(); } } catch (Throwable ex) { // Ignore. No java.util.logging bridge is installed. } }
ApplicationEnvironmentPreparedEvent–> 最终会调用Log4J2LoggingSystem#initialize.
ApplicationPreparedEvent事件–> 向beanFactory进行注册.
- ContextClosedEvent–>执行Log4J2LoggingSystem#cleanUp方法.
jvm退出前,执行Log4J2LoggingSystem注册的ShutdownHandler,将LoggerContext停止掉.代码如下:
public void run() { getLoggerContext().stop(); }